# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import functools
import sys
import urllib
import json

from tornado.ioloop import IOLoop

from coin.base.config import Config
from coin.flow.simple_queue import SimpleQueue
import coin.flow.subscriber as flow_subscriber

import coin.exchange.huobi.symbology as huobi_symbology
from coin.exchange.huobi.order_gateway import HuobiOrderGateway
from coin.exchange.binance.order_gateway import BinanceOrderGateway
from coin.exchange.bitfinex_v2.order_gateway import BitfinexV2OrderGateway

from coin.strategy.arb import binance_huobi_btc
from coin.strategy.arb import binance_huobi_usdt
from coin.strategy.arb import bitfinex_binance_btc
from coin.strategy.arb import bitfinex_binance_usdt
from coin.exchange.shared.kr_rest.product import SharedProduct
from coin.exchange.huobi.kr_rest.product import HuobiProduct
from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct

from coin.experimental.inkyu.log_util import LogParser

price_map = {}
global_result = []


def timer_callback(quote, reserve, binance_og, huobi_og, products, ioloop):
  if huobi_og.is_ready() and binance_og.is_ready():
    bnb_position = binance_og.get_position()['bnb']
    global_result.append('BNB: %s' % bnb_position)
    binance_position = binance_og.get_position()[quote]
    huobi_position = huobi_og.get_position()[quote]
    quote_position = binance_position + huobi_position
    global_result.append('%s: binance(%.8f), huobi(%.8f), total(%.8f)' %
                         (quote, binance_position, huobi_position, quote_position))
    for product in products:
      binance_position = binance_og.get_position()[product.base.lower()]
      huobi_position = huobi_og.get_position()[product.base.lower()]
      total_position = binance_position + huobi_position
      reserve_position = reserve[product.base]
      compensated = (total_position - reserve_position) * price_map[product]
      global_result.append('%s: binance(%.8f), huobi(%.8f), total(%.8f/%.2f) => %s(%f)' %
                           (product.base,
                            binance_position,
                            huobi_position,
                            total_position,
                            reserve_position,
                            quote,
                            compensated))
      quote_position += compensated
    global_result.append('current %s: %f' % (quote, quote_position))
    global_result.append('')
    ioloop.stop()
  else:
    ioloop.add_timeout(
        datetime.timedelta(seconds=3),
        functools.partial(timer_callback, quote, reserve, binance_og, huobi_og, products, ioloop))


def run_huobi_binance(quote: str, queue, ioloop, flow_sub):
  products = None
  reserve = None
  if quote == 'btc':
    products = [SharedProduct.FromStr(symbol) for symbol in binance_huobi_btc.SYMBOLS]
    reserve = binance_huobi_btc.RESERVE_POSITIONS
  elif quote == 'usdt':
    products = [SharedProduct.FromStr(symbol) for symbol in binance_huobi_usdt.SYMBOLS]
    reserve = binance_huobi_usdt.RESERVE_POSITIONS
  else:
    raise ValueError(quote)
  # TODO(inkyu): Use view-key
  huobi_products = [HuobiProduct.FromProduct(product) for product in products]
  binance_products = [BinanceProduct.FromProduct(product) for product in products]

  huobi_og = HuobiOrderGateway(huobi_products, Config('huobi.%s' % quote))
  binance_og = BinanceOrderGateway(binance_products, Config('binance.%s' % quote))
  huobi_og.start(queue, ioloop, flow_sub)
  binance_og.start(queue, ioloop, flow_sub)

  global_result.append('huobi-binance: %s' % quote)
  ioloop.add_timeout(
      datetime.timedelta(seconds=3),
      functools.partial(timer_callback, quote, reserve, binance_og, huobi_og, products, ioloop))
  ioloop.start()


def timer_callback_bitfinex(quote, reserve, bitfinex_og, binance_og, products, ioloop):
  if bitfinex_og.is_ready() and binance_og.is_ready():
    bnb_position = binance_og.get_position()['bnb']
    global_result.append('BNB: %s' % bnb_position)
    binance_position = binance_og.get_position()[quote]
    bitfinex_position = bitfinex_og.get_position()['usd']
    quote_position = binance_position + bitfinex_position
    global_result.append('%s: bitfinex(%.8f), binance(%.8f), total(%.8f)' %
                         (quote, bitfinex_position, binance_position, quote_position))
    for product in products:
      bitfinex_product = BitfinexProduct.FromProduct(product)

      binance_position = binance_og.get_position()[product.base.lower()]
      bitfinex_position = bitfinex_og.get_position()[product.base.lower()]
      total_position = binance_position + bitfinex_position
      reserve_position = reserve[product.base]
      compensated = (total_position - reserve_position) * price_map[product]
      global_result.append('%s: bitfinex(%.8f), binance(%.8f), total(%.8f/%.2f) => %s(%f)' %
                           (product.base,
                            bitfinex_position,
                            binance_position,
                            total_position,
                            reserve_position,
                            quote,
                            compensated))
      quote_position += compensated
    global_result.append('current %s: %f' % (quote, quote_position))
    global_result.append('')
    ioloop.stop()
  else:
    ioloop.add_timeout(
        datetime.timedelta(seconds=1),
        functools.partial(timer_callback_bitfinex,
                          quote,
                          reserve,
                          bitfinex_og,
                          binance_og,
                          products,
                          ioloop))


def run_bitfinex_binance(quote: str, queue, ioloop, flow_sub):
  products = None
  reserve = None
  if quote == 'btc':
    products = [SharedProduct.FromStr(symbol) for symbol in bitfinex_binance_btc.SYMBOLS]
    reserve = bitfinex_binance_btc.RESERVE_POSITIONS
  elif quote == 'usdt':
    products = [SharedProduct.FromStr(symbol) for symbol in bitfinex_binance_usdt.SYMBOLS]
    reserve = bitfinex_binance_usdt.RESERVE_POSITIONS
  else:
    raise ValueError(quote)
  # TODO(inkyu): Use view-key
  bitfinex_products = [BitfinexProduct.FromProduct(product) for product in products]
  binance_products = [BinanceProduct.FromProduct(product) for product in products]

  bitfinex_og = BitfinexV2OrderGateway(bitfinex_products,
                                       Config('bitfinex.bitfinex-binance.%s' % quote))
  binance_og = BinanceOrderGateway(binance_products, Config('binance.bitfinex-binance.%s' % quote))
  bitfinex_og.start(queue, ioloop, flow_sub)
  binance_og.start(queue, ioloop, flow_sub)

  quote = 'usdt'
  global_result.append('bitfinex-binance: %s' % quote)
  ioloop.add_timeout(
      datetime.timedelta(seconds=1),
      functools.partial(timer_callback_bitfinex,
                        quote,
                        reserve,
                        bitfinex_og,
                        binance_og,
                        products,
                        ioloop))
  ioloop.start()


def main():
  global_result.append('%s' % datetime.datetime.now())
  lp = LogParser()

  url = 'https://api.coinmarketcap.com/v1/ticker/?limit=0'
  json_text = urllib.request.urlopen(url).read()
  product_list = json.loads(json_text)
  for product in product_list:
    currency = product['symbol']
    if currency in ('BTC', 'ETH', 'NEO'):
      price_map[SharedProduct(currency, 'USDT')] = float(product['price_usd'])

  queue = SimpleQueue('q')
  flow_sub = flow_subscriber.from_queue(queue)
  ioloop = IOLoop.current()
  run_huobi_binance('usdt', queue, ioloop, flow_sub)
  run_bitfinex_binance('usdt', queue, ioloop, flow_sub)
  run_bitfinex_binance('btc', queue, ioloop, flow_sub)

  lp.send('\n'.join(global_result))
  return 0


if __name__ == '__main__':
  sys.exit(main())
